/**
 *	The NeuroCoSA Toolkit
 *	Copyright (C) 2003-6 Stuart Meikle.
 *
 *	This is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation; either
 *	version 2.1 of the License, or (at your option) any later version.
 *
 *	This library is distributed in the hope that it will be useful,
 *	but WITHOUT ANY WARRANTY; without even the implied warranty of
 *	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *	Lesser General Public License for more details.
 *
 * @author	Stuart Meikle
 * @version	2006-halloween(mk2)
 * @license	LGPL
 */
package org.stumeikle.NeuroCoSA.NIS;
import java.io.*;
import org.stumeikle.NeuroCoSA.PubSub.TimeLord;
import org.stumeikle.NeuroCoSA.PubSub.Subscriber;

/*
 * new class 20071025. stumeikle
 * need to enable more buffereing , more flexibility
 * will add the ability to recover lots of data, to specify (automaticaly or otherwise)
 * the size of the buffer and will add a shelf life var so that we know when data becomes
 * invalid. anticipated as needed for hippocampus stuff. 
 * Algo: 20071027
 * see inline
 */

import java.util.*;


//20071211 updating this now. need to rewrite NIS to be clearer, understanding that clients will need to
//specify the time for the data. also using the interface to supersynchedatom to enable a common base class.


/** essentially "yesterdays news" class converted to inherit nisatom. no multiple inheritence huh.
 */
public class InfoWithMultiStore extends Info
{
    //store values in a linked list. pop values from the front of the list when they are too old
    //each incoming object will need to be wrapped so that time can also be stored
    class	TimeStampedValue
    {
	Object			iValue;
	long			iTimeStamp;

	TimeStampedValue(Object o, long t) { iValue = o; iTimeStamp = t; }

	long			getTime() { return iTimeStamp; }
	void			setTime(long l ) { iTimeStamp =l; }
	Object			getValue() { return iValue; }
	void			setValue( Object o ) { iValue=o; }
    }

    protected  LinkedList<TimeStampedValue>	
				iBuffer; //first item in the list is the current time...
    protected  TimeStampedValue iLastValue;
    protected  long		iLastUpdateTime;
    protected  long		iSlowestConsumersDelta;
    protected  long		iLargestDeltaStored; // = fn(iSlowestConsumersDelta)
    protected  long		iValueShelfLife; //how many ticks values remain valid for
    protected  TimeLord		iTimeLord;
    protected  long		iGarbageCount;

    public InfoWithMultiStore(String n, TimeLord t)
    {
	super(n);
	iTimeLord=t;

	iBuffer = new LinkedList<TimeStampedValue> ();
	iSlowestConsumersDelta = 1;
	iLargestDeltaStored = 1; //we always store now and previous values
	iValueShelfLife = 2; //make sure that we Can read data set last time
	iLastValue = null;
	iLastUpdateTime = 0;
	iGarbageCount = 0;
    }

    //overload the standard methods.
    //setValue () => store value at current time 
    //getValue () => return value stored at current time if poss else current time -1. 
    //getValue ( time t ) => return value stored at time given - 1. Update slowest consumers delta
    //		             and iLargestDeltaStored as needed. ie increase buffer size for slow 
    //                       consumers
    //updateList() => remove entries from the start of the list which are older than current time - 
    //		      iLargestDeltaStored

    public synchronized void	setValue(Object av)
    {
	long	 now = iTimeLord.getTime();

	//(1) allow us to send multiple updates to this value at the same time.
	//only store the last value.
	lock(this);
	if (now == iLastUpdateTime)
        {
	    //in this case just update the structure at the end of the list
System.out.println("NISSuperSynchedAtom: updating item at end of list");
	    iLastValue.setValue(av);
	}
	else
	{
	     //time has passed.
	     TimeStampedValue	t = new TimeStampedValue(av, now);
	     iBuffer.addFirst(t);
	     iLastValue = t;
System.out.println("NISSuperSynchedAtom: adding new item to beginning of list");
	     iLastUpdateTime = now;
	}

	iGarbageCount++;
	if (iGarbageCount>iLargestDeltaStored)
	{ iGarbageCount = 0; collectGarbage(); }

	releaseAndNotify(this);
    }

    public synchronized Object	getValue()
    {
	//return value at 'now'
	long 	t = iTimeLord.getTime();
	
	return getValue( t );
    }
    
    public Object		getValue( long t )
    {
	if (iLocker!=null)	//someone is updating the data currently
	{
	    //if someone has locked the data then wait
	    try{ wait() ; } 
	    catch(Exception e){}
	}


	//minor mod to help callers
	if (t<=0)
	{  t+= iTimeLord.getTime(); } 

	//used by slow consumers
	//(1) update vars based on 't'. (if we're being asked for ancient data update buffer 
	//length)
	long	delta = iTimeLord.getTime() - t; 

	System.out.println("NISSuperSynched: delta time is " + delta);
	if (delta<0)		return null;//future
	if (delta>iSlowestConsumersDelta)
	{
	    iSlowestConsumersDelta = delta;
	    iLargestDeltaStored = 2*iSlowestConsumersDelta; //should be k*iSlowestconsumersdelta
	    System.out.println("NISSuperSynched: changing buffer size to " + iLargestDeltaStored);
	}

	//(2) progress through the list until we find the right value and then return it
	ListIterator<TimeStampedValue>	i = iBuffer.listIterator(0);//skip current value
	
	for(;i.hasNext();)
	{
	    TimeStampedValue	tsv = (TimeStampedValue) i.next();
	    long		tsvt = tsv.getTime();

	    if (t >=tsvt && t< (tsvt+iValueShelfLife))
	    {
		return tsv.getValue();
	    }
	}

	//else
	return null;
    }

    protected void		collectGarbage()
    {
	//remove items from the end of the list depending on our criteria
	//ie items which are older than we are prepared to keep need to be removed
	//this method could take time = k*buffersize to complete
	TimeStampedValue	tsv;
	long			now = iTimeLord.getTime();
	boolean			exitloop =false;

	do
	{
	    tsv= iBuffer.getLast();
	    if ((now-tsv.getTime()) > iLargestDeltaStored)
	    {
		//delta is too big, remove the data
		System.out.println("NISSuperSynched: removing old data");
		iBuffer.removeLast();
		if (iBuffer.size()==0)	exitloop=true;
	    }
	    else
		exitloop =true;
	}while (exitloop==false);
    }
}

